﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using MPI;


namespace DwarfConfigurator
{
    class BuilderMpi : IBuilder
    {
        /// <summary>
        /// The settings for this dwarf.
        /// </summary>
        private Settings settings;

        private DataModel model;
        /// <summary>
        /// Get settings from the command line.
        /// </summary>
        /// <param name="args">
        /// Cli params.
        /// </param>
        /// <param name="solver">
        /// Instance of Solver.
        /// </param>
        public void buildSettings(String[] args, DataModel model)
        {
            bool error = false;
            if (model.isRootThread)
            {
                try
                {
                    settings = new Settings();
                    settings.Init(args, model);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    error = true;
                }
            }
            //Distribute error if that occurred while parsing.
            Communicator.world.Broadcast(ref error, DataModel.ROOT_PROCESS);
            if (error) throw new Exception("");

            int n = 0;
            int m = 0;
            int t = 0;
            if (model.isRootThread)
            {
                n = model.hmm.getNumberOfStates();
                m = model.hmm.getNumberOfObservation();
                t = model.vit.getLengthOfObservationSequence();
            }
            //Distribute the number of states.
            Communicator.world.Broadcast(ref n, DataModel.ROOT_PROCESS);
            //Distribute the number of observation.
            Communicator.world.Broadcast(ref m, DataModel.ROOT_PROCESS);
            //Distribute the length of observation sequence.
            Communicator.world.Broadcast(ref t, DataModel.ROOT_PROCESS);

            //Initializing of slave instances
            if (!model.isRootThread)
            {
                model.init(n, m, t);
            }

            //Distribute  the A and B matrices to all instances
            int lengthOfStates = model.hmm.getNumberOfStates();
            for (int i = 0; i < lengthOfStates; i++)
            {
                Communicator.world.Broadcast(ref model.hmm.stateTransitionMatrix[i], DataModel.ROOT_PROCESS);
                Communicator.world.Broadcast(ref model.hmm.observationProbabilityMatrix[i], DataModel.ROOT_PROCESS);
            }

            //Distribute  O vector to all instances.
            Communicator.world.Broadcast(ref model.vit.observationSequence, DataModel.ROOT_PROCESS);

            /*Set up of balance*/
            if (Communicator.world.Rank == (Communicator.world.Size - 1))
            {
                model.lengthForProc = model.hmm.getNumberOfStates() - (model.hmm.getNumberOfStates() / Communicator.world.Size) * Communicator.world.Rank;
                model.startIndx = model.hmm.getNumberOfStates() - model.lengthForProc;
                model.endIndx = model.hmm.getNumberOfStates();
            }
            else
            {
                model.lengthForProc = model.hmm.getNumberOfStates() / Communicator.world.Size;
                model.startIndx = model.lengthForProc * Communicator.world.Rank;
                model.endIndx = model.lengthForProc * (Communicator.world.Rank + 1);
            }
            model.arrayOflengthForProc = new int[Communicator.world.Size];
            Communicator.world.Gather(model.lengthForProc, DataModel.ROOT_PROCESS, ref model.arrayOflengthForProc);
            Communicator.world.Broadcast(ref model.arrayOflengthForProc, DataModel.ROOT_PROCESS);
            /*End of setup*/
        }

        public Settings getSetting()
        {
            return settings;
        }

        public DataModel getModel()
        {
            return model;
        }

        public DataModel process(String[] args)
        {
            try
            {
                if (Communicator.world.Size == 1)
                {
                    throw new Exception("Only one mpi process.");
                }

                model = new DataModel();
                if (Communicator.world.Rank == DataModel.ROOT_PROCESS)
                {
                    model.isRootThread = true;
                }

                buildSettings(args, model);                               // Get settings from the command line.


            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                usage();
            }

            return model;
        }

        /// <summary>
        /// Write the rules of command line structure.
        /// </summary>
        [DllImport(@"CliTools.dll")]
        private static extern void usage();
    }
}
